package com.sxpi.user.service.useramount;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sxpi.common.result.R;
import com.sxpi.user.dto.UserAmountDto;
import com.sxpi.user.mapper.UserAmountRecordMapper;
import com.sxpi.user.mapper.UserMapper;
import com.sxpi.user.pojo.User;
import com.sxpi.user.pojo.UserAmountRecord;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Author: 董伟豪
 * @Date: 2022/11/8 21:14
 */
@Service
@Slf4j
public class IUserAmountRecordRedissonServiceImpl extends ServiceImpl<UserAmountRecordMapper, UserAmountRecord>
        implements IUserAmountRecordRedissonService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 提现方法---redisson锁（可重入）
     * redisson底层采用lua脚本和看门狗来实现 分布式锁机制的
     * @param userAmountDto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    // @GlobalTransactional
    public R takeMoneyRedissonLock(UserAmountDto userAmountDto) {
        // 提现的用户
        Long userId = userAmountDto.getUserId();
        // 提现的金额
        Double money = userAmountDto.getMoney();

        String key = "user:ammount:key:" + userId;
        String value = System.nanoTime() + "_" + UUID.randomUUID();

        RLock lock = redissonClient.getLock(key);

        // 红锁（用于集群，防止一个节点挂掉之后）
        // redissonClient.getRedLock(lock1,lock2,lock3);
        try{
            log.info("1--------------->开始获得锁...");
            // 这里通过lock.lock()获取分布式锁
            // 上锁之后，不管何种情况20秒之后自动释放    排队(可重入锁)
            // 这里还有tryLock
            lock.lock(20L, TimeUnit.SECONDS);

            log.info("2--------------->开始获得锁...获取锁成功!!!!");

            User user = userMapper.selectById(userId);
            // 判断用户提现的余额是否充足，如果充足就提现
            if(user != null && user.getAmount().doubleValue() - money > 0){
                // 如果余额充足，就开始扣减余额
                User updateUser = new User();
                updateUser.setId(userId);
                updateUser.setAmount(new BigDecimal(user.getAmount().doubleValue() - money));
                // 这行代码只是为了同步一些余额，避免再次查询数据库
                user.setAmount(updateUser.getAmount());
                // 更新用户余额
                userMapper.updateById(updateUser);

                // 同时插入提现记录信息保存到用户提现表中
                UserAmountRecord userAmountRecord = new UserAmountRecord();
                userAmountRecord.setUserId(userId);
                userAmountRecord.setUsername(user.getUsername());
                userAmountRecord.setAvatar(user.getAvatar());
                userAmountRecord.setMoney(new BigDecimal(money));
                // 保存用户提现记录
                this.saveOrUpdate(userAmountRecord);

                // 输出日志
                log.info("当前提现的金额是：{}，用户的余额是：{}", money, user.getAmount());
            }else{
                // 否则就返回账户余额不足
                return R.error("账户不存在，或者余额不足!!!");
            }
            return R.ok();

        }catch (Exception ex){
            return R.error("执行失败");
            // 这里一定要发邮件

        }finally {
            log.info("3--------------->释放锁!!!!");
            // 释放锁
            lock.unlock();
            // 强制释放锁
            lock.forceUnlock();
        }
    }

    /**
     * 提现方法---redisson锁（不可重入）
     * @param userAmountDto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    // @GlobalTransactional
    public R takeMoneyRedissonTryLock(UserAmountDto userAmountDto) {
        // 提现的用户
        Long userId = userAmountDto.getUserId();
        // 提现的金额
        Double money = userAmountDto.getMoney();

        String key = "user:ammount:key:" + userId;
        String value = System.nanoTime() + "_" + UUID.randomUUID();

        RLock lock = redissonClient.getLock(key);
        boolean isLock = false;
        try{
            // 这里通过lock.lock()获取分布式锁
            // 上锁之后，不管何种情况20秒之后自动释放    排队(可重入锁)
            // 这里还有tryLock
            log.info("1--------------->开始获得锁...");
            isLock = lock.tryLock(20L, TimeUnit.SECONDS);
            if(isLock){
                log.info("2--------------->开始获得锁...获取锁成功!!!!");
                User user = userMapper.selectById(userId);
                // 判断用户提现的余额是否充足，如果充足就提现
                if(user != null && user.getAmount().doubleValue() - money > 0){
                    // 如果余额充足，就开始扣减余额
                    User updateUser = new User();
                    updateUser.setId(userId);
                    updateUser.setAmount(new BigDecimal(user.getAmount().doubleValue() - money));
                    // 这行代码只是为了同步一些余额，避免再次查询数据库
                    user.setAmount(updateUser.getAmount());
                    // 更新用户余额
                    userMapper.updateById(updateUser);

                    // 同时插入提现记录信息保存到用户提现表中
                    UserAmountRecord userAmountRecord = new UserAmountRecord();
                    userAmountRecord.setUserId(userId);
                    userAmountRecord.setUsername(user.getUsername());
                    userAmountRecord.setAvatar(user.getAvatar());
                    userAmountRecord.setMoney(new BigDecimal(money));
                    // 保存用户提现记录
                    this.saveOrUpdate(userAmountRecord);

                    // 输出日志
                    log.info("当前提现的金额是：{}，用户的余额是：{}", money, user.getAmount());
                }else{
                    // 否则就返回账户余额不足
                    return R.error("账户不存在，或者余额不足!!!");
                }
                return R.ok();
            }else{
                log.info("没有拿到，离开了.......");
                return R.error("别着急，请慢一点....");
            }
        }catch (Exception ex){
            return R.error("执行失败");
            // 这里一定要发邮件

        }finally {
            log.info("3--------------->释放锁!!!!");
            if(isLock){
                // 释放锁
                lock.unlock();
                // 强制释放锁
                lock.forceUnlock();
            }


        }
    }
}
